package de.saarland.uni.lego3d.server.gui;

import java.awt.Color;

import javax.media.j3d.Appearance;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.ColoringAttributes;
import javax.media.j3d.ImageComponent2D;
import javax.media.j3d.LineAttributes;
import javax.media.j3d.Material;
import javax.media.j3d.Texture;
import javax.media.j3d.Texture2D;
import javax.media.j3d.TextureAttributes;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.vecmath.Color3f;
import javax.vecmath.Color4f;
import javax.vecmath.Vector3f;

import sun.reflect.generics.reflectiveObjects.NotImplementedException;

import com.sun.j3d.utils.geometry.Box;
import com.sun.j3d.utils.image.TextureLoader;

import de.saarland.uni.lego3d.server.Legobrick;

/**
 * This class represents a virtual lego brick in the 3D space.
 */
public class LegoBrick3D extends BranchGroup {

	/**
	 * Stores the rotation on degrees The method rotate() will cause a left turn
	 * of 90 degrees TODO: It works, but we have not defined a gesture for this
	 * interaction
	 */
	private int rotation = 0;

	/**
	 * current position to compute collisionss
	 */
	private Vector3f currentPosition = new Vector3f(0, 0, 0);

	/**
	 * Flag if this lego brick was already composed. This flag is used to be
	 * able to move temporary compsed models (right panel in the MainGUI) in one
	 * piece and skip the detailed interaction of single bricks
	 */
	public boolean composed = false;

	/**
	 * Transform group needed to move and rotate this lego brick representation
	 */
	private TransformGroup transformGroup = new TransformGroup();

	/**
	 * 3D transform object to transform the lego brick (move and rotate)
	 */
	private Transform3D T3D = new Transform3D();

	/**
	 * The underlying LegoBrick Object
	 */
	public Legobrick brickDef;

	/**
	 * destination vector to compute the collisions
	 */
	private Vector3f destination = new Vector3f(0, 0, 0);
	private Vector3f oldDestination = new Vector3f(0, 0, 0);

	/**
	 * flag indicating whether the brick can be moved or not
	 */
	private boolean canMove = true;

	/**
	 * Creates a new lego 3D brick with the given parameters.
	 * 
	 * @param Legobrick
	 *            - the underlying legobrick defining the layout of the virtual
	 *            brick
	 */
	public LegoBrick3D(Legobrick brick) {
		this.brickDef = brick;

		// Used to centralize the new brick in front of the interaction area
		// TransformGroup transformGroup;

		// capabilities to set
		transformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
		transformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
		setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
		setCapability(BranchGroup.ALLOW_CHILDREN_READ);
		setCapability(BranchGroup.ALLOW_DETACH);
		setCapability(BranchGroup.ALLOW_PICKABLE_READ);
		setCapability(BranchGroup.ALLOW_PICKABLE_WRITE);

		this.setName("LegoBrick");

		// color
		Appearance app = new Appearance();
		Material material = new Material();
		material.setAmbientColor(0.0f, 0.0f, 0.0f);
		material.setEmissiveColor(0.15f, 0.15f, 0.15f);
		material.setDiffuseColor(new Color3f(new Color(brickDef.color)));
		material.setSpecularColor(0.1f, 0.1f, 0.1f);
		material.setShininess(5);
		app.setMaterial(material);
		app.setColoringAttributes(new ColoringAttributes(new Color3f(new Color(
				brickDef.color)), ColoringAttributes.NICEST));
		app.setLineAttributes(new LineAttributes(1f,
				LineAttributes.PATTERN_SOLID, true));

		// Add texture
		Appearance texapp = new Appearance();
		texapp.setMaterial(material);
		Texture tex = null;
		if (brickDef.getLayout()[0] == 1) {
			tex = createTexture("/de/saarland/uni/lego3d/textures/tex_"
					+ brickDef.getLayout().length + ".png");
		} else {
			tex = createTexture("/de/saarland/uni/lego3d/textures/tex_"
					+ brickDef.getLayout().length + "d" + ".png");
		}
		texapp.setTexture(tex);
		TextureAttributes ta = new TextureAttributes(TextureAttributes.BLEND,
				new Transform3D(), new Color4f(new Color(brickDef.color)),
				TextureAttributes.NICEST);
		texapp.setTextureAttributes(ta);

		// Create main box according to brick layout
		Box basicbox = new Box(brickDef.getLayout().length * 0.05f,
				brickDef.getHeight() * 0.075f, brickDef.getLayout()[0] * 0.05f,
				Box.GENERATE_TEXTURE_COORDS, app);

		// set the texture appearance only on the top
		basicbox.getShape(Box.TOP).setAppearance(texapp);

		// Move the brick at the entry point (centralized in front of the
		// interaction area)
		T3D = new Transform3D();
		// TODO
		// fix for correct positioning while collision, if x component is 0 then
		// there will be always one step between two bricks colliding in x
		// direction
		T3D.setTranslation(new Vector3f(brickDef.getLayout().length * 0.05f,
				brickDef.getHeight() * 0.075f, brickDef.getLayout()[0] * 0.05f));
		transformGroup.setTransform(T3D);
		transformGroup.addChild(basicbox);
		this.addChild(this.transformGroup);

		// Add to private transformgroup
		// this.transformGroup.addChild(transformGroup);
	}

	/**
	 * Creates a new lego3D brick. This constructor is used, if this brick is
	 * loaded from a temporary file and, therefore, is a composed object.
	 * 
	 * @param bg_
	 *            - the root, that described the composed object
	 */
	public LegoBrick3D(BranchGroup bg_) {

		this.setName("LegoBrickLoaded");

		// set the capabilities
		transformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
		transformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
		setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
		setCapability(BranchGroup.ALLOW_CHILDREN_READ);
		setCapability(BranchGroup.ALLOW_DETACH);
		setCapability(BranchGroup.ALLOW_PICKABLE_READ);
		setCapability(BranchGroup.ALLOW_PICKABLE_WRITE);

		this.composed = true;

		this.transformGroup.addChild(bg_);
		this.addChild(this.transformGroup);
	}

	/**
	 * Empty constructor
	 */
	public LegoBrick3D() {
		// NO-ARGS to fix error for Java3D internals
	}

	/**
	 * Creates a texture object.
	 * 
	 * @param filename
	 *            - the filename of the texture file.
	 * @return the texture object.
	 */
	private Texture createTexture(String filename) {
		// Load the texture
		TextureLoader loader = new TextureLoader(this.getClass().getResource(
				filename), null);
		ImageComponent2D image = loader.getImage();

		if (image == null) {
			System.out
					.println("load failed for texture: de.saarland.uni.lego3d.textures."
							+ filename);
		}

		// can't use parameterless constuctor
		Texture2D texture = new Texture2D(Texture.BASE_LEVEL, Texture.RGBA,
				image.getWidth(), image.getHeight());
		texture.setImage(0, image);

		texture.setBoundaryModeT(Texture.CLAMP);
		texture.setBoundaryModeS(Texture.WRAP);

		return texture;
	}

	/**
	 * Moves the lego brick to the direction where the parameter is unequal 0.
	 * Example: (1,1,0) will cause this brick to move one cell in X and one cell
	 * in Y direction
	 * 
	 * @param x
	 *            - x direction
	 * @param y
	 *            - y direction
	 * @param z
	 *            - z direction
	 */
	public void move(float x, float y, float z) {
		// getting the old vector
		Vector3f tempOld = new Vector3f();
		this.T3D.get(tempOld);

		// adding the new destination
		float newX = tempOld.x + (x * 0.1f);
		float newY = tempOld.y + (y * 0.1f);
		float newZ = tempOld.z + (z * 0.075f);

		// prevent from moving under the lego field
		if (newY < 0.07) {
			newY = 0.075f;
			y = 0;
		}

		// Vector3f oldPos = new Vector3f(destination);

		try {

			// TODO: Make this more stable
			if (brickDef != null) {

				// create a temporary destination vector for collision handling
				Vector3f tempdest = new Vector3f(currentPosition);
				tempdest.add(new Vector3f(x, y, z));
				destination = new Vector3f(tempdest);
				// destination.add(new Vector3f(x, y, z));

				/********************************* DEBUGGING SYSOUTS ***************************************/
				// for (int i = 0; i < 50; i++) {
				// for (int j = 0; j < 30; j++) {
				// for (int k = 0; k < 34; k++) {
				// if (MainGUI.legoRoom[i][j][k] == 1) {
				// System.out.println("contains 1 at: " + i + ", " + j
				// + ", " + k);
				// }
				// }
				// }
				// }

				// System.out.println("current position: " +
				// currentPosition.toString());
				// System.out.println("old destination: " +
				// oldDestination.toString());
				// System.out.println("destination: " + destination);

				/*****************************************************************************************/

				// TODO: works fine with backward movement, for moving left and
				// right, a
				// case differentiation has to be done according to the move
				// coordinates
				// wenn nur eine koordinate sich �ndert muss nur diese
				// richtung
				// betrachtet werden, wenn sich mehrere �ndern, dann auch
				// diese -->
				// �nderung wenn coord != 0

				/******************************* CHECK METHODS FOR X DIRECTION ****************************/

				// checks if we can move in x direction, if we couldn't move in
				// the step
				// before
				if (!canMove && x != 0) {
					boolean tmp = true;
					// check if the old destination is free
					for (int j = 0; j < brickDef.getHeighestDepth(); j++) {
						// first we check if we move to the left or to the right
						// and
						// then we check if the position to the left or to the
						// right is
						// free (that means the array slot is 0)
						if (destination.x < currentPosition.x) {
							if (MainGUI.legoRoom[25 /*- width / 2*/
							+ (int) (destination.x)][(int) Math
									.abs(destination.y)][2
									+ (int) Math.abs(destination.z) - j] == 1) {

								// ******************************DEBUGGING*********************************//
								// System.out.println("canMove: " + canMove);
								// System.out.println("want to left: "
								// + (25 /*- width / 2*/+ (int) (destination.x))
								// + ", " + ((int) Math.abs(destination.y)) +
								// ", "
								// + ((int) Math.abs(destination.z)));
								// System.out.println("cannot move further");

								// ***********************************************************************//

								tmp = false;
							}
						} else {
							if (MainGUI.legoRoom[25 + (int) (destination.x)
									+ (brickDef.getLayout().length - 1)][2 + (int) Math
									.abs(destination.y)][2
									+ (int) Math.abs(destination.z) - j] == 1) {

								// ******************************DEBUGGING*********************************//
								// System.out.println("canMove: " + canMove);
								// System.out
								// .println("want to right: "
								// + (25 + (width - 1) /*- width / 2*/+ (int)
								// (destination.x))
								// + ", "
								// + ((int) Math.abs(destination.y))
								// + ", "
								// + ((int) Math.abs(destination.z)));

								// System.out.println("cannot move further");

								// ***********************************************************************//

								tmp = false;
							}
						}

					}
					canMove = tmp;
				}

				// //checks if we now can move in x direction
				if (canMove && x != 0) {
					// for (int i = 0; i < width; i++) {
					for (int j = 0; j < brickDef.getHeighestDepth(); j++) {
						if (destination.x < currentPosition.x) {
							System.out
									.println("want to left: "
											+ (25 /*- width / 2*/+ (int) (destination.x))
											+ ", "
											+ ((int) Math.abs(destination.y))
											+ ", "
											+ (2 + (int) Math
													.abs(destination.z)));
							if (MainGUI.legoRoom[25 /*- width / 2*/
							+ (int) (destination.x)][(int) Math
									.abs(destination.y)][2
									+ (int) Math.abs(destination.z) - j] == 1) {
								canMove = false;
								oldDestination = new Vector3f(destination);

								// ******************************DEBUGGING*********************************//
								// System.out.println("want to left: "
								// + (25 /*- width / 2*/+ (int) (destination.x))
								// + ", " + ((int) Math.abs(destination.y)) +
								// ", "
								// + (2 + (int) Math.abs(destination.z)));
								//
								// ***********************************************************************//

								System.out.println("cannot move further in x");
							}
							// }
						} else {
							System.out
									.println("want to right: "
											+ (25 /*- width / 2*/+ (int) (destination.x) + (brickDef
													.getLayout().length - 1))
											+ ", "
											+ ((int) Math.abs(destination.y))
											+ ", "
											+ (2 + (int) Math
													.abs(destination.z)));
							if (MainGUI.legoRoom[25 + (int) (destination.x)
									+ (brickDef.getLayout().length - 1)][(int) Math
										.abs(destination.y)][2
									+ (int) Math.abs(destination.z) - j] == 1) {
								canMove = false;
								oldDestination = new Vector3f(destination);

								// ******************************DEBUGGING*********************************//
								// System.out
								// .println("want to right: "
								// + (25 /*- width / 2*/+ (int) (destination.x)
								// + (width
								// - 1))
								// + ", "
								// + ((int) Math.abs(destination.y))
								// + ", "
								// + (2 + (int) Math.abs(destination.z)));
								//
								// ***********************************************************************//

								System.out.println("cannot move further in x");
							}
						}
					}
				}

				/******************************* END CHECK METHODS FOR X DIRECTION *******************************/

				/******************************* CHECK METHODS FOR Z DIRECTION *******************************/

				// checks if we can move in z direction, if we couldn't move in
				// the step
				// before
				if (!canMove && z != 0) {
					boolean tmp = true;
					// check if the old destination is free
					int offset = 0;
					// first we check if we move backward or forward and set the
					// offset,
					// then we check if the position to the front or to the back
					// is
					// free (that means the array slot is 0)
					if (destination.z > currentPosition.z) {
						offset = brickDef.getHeighestDepth() - 1;
					}
					for (int i = 0; i < brickDef.getLayout().length; i++) {
						// for (int j = 0; j < heighestDepth; j++) {
						if (MainGUI.legoRoom[25 /*- width / 2*/
								- (int) Math.abs(destination.x) + i][(int) Math
									.abs(destination.y)][2
								+ (int) Math.abs(destination.z) - offset] == 1) {

							// ******************************DEBUGGING*********************************//
							// System.out.println("canMove: " + canMove);
							// System.out.println("want to: " + (25 - width / 2
							// + (int) Math.abs(destination.x) + i) + ", " +
							// ((int) Math
							// .abs(destination.y)) + ", " + ((int) Math
							// .abs(destination.z)));
							// ***********************************************************************//

							tmp = false;
							System.out.println("cannot move further in z");
							break;
						}
						// }
					}
					canMove = tmp;
				}

				// //checks if we now can move in z direction
				if (canMove && z != 0) {
					int offset = 0;
					if (destination.z > currentPosition.z) {
						offset = brickDef.getHeighestDepth() - 1;
					}
					for (int i = 0; i < brickDef.getLayout().length; i++) {
						// for (int j = 0; j < heighestDepth; j++) {
						if (MainGUI.legoRoom[25 /*- width / 2*/
								- (int) Math.abs(destination.x) + i][(int) Math
									.abs(destination.y)][2
								+ (int) Math.abs(destination.z) - offset] == 1) {
							// System.out.println("canMove: " + canMove);
							canMove = false;
							oldDestination = new Vector3f(destination);

							// ******************************DEBUGGING*********************************//
							// System.out.println("want to: " + (25 - width / 2
							// + (int) Math.abs(destination.x) + i) + ", " +
							// ((int) Math
							// .abs(destination.y)) + ", " + ((int) Math
							// .abs(destination.z)));
							// ***********************************************************************//

							System.out.println("cannot move further in z");
							break;
						}
						// }
					}
				}

				/******************************* END CHECK METHODS FOR Z DIRECTION *******************************/

				/******************************* CHECK METHODS FOR Y DIRECTION *******************************/

				// checks if we can move in y direction, if we couldn't move in
				// the step
				// before
				if (!canMove && y != 0) {
					boolean tmp = true;
					// check if the old destination is free
					for (int i = 0; i < brickDef.getLayout().length; i++) {
						for (int j = 0; j < brickDef.getHeighestDepth(); j++) {
							// we check if the position above or to below is
							// free (that means the array slot is 0)
							if (MainGUI.legoRoom[25 /*- width / 2*/
									- (int) Math.abs(destination.x) + i][(int) Math
										.abs(destination.y)][2 + (int) Math
									.abs(destination.z)] == 1) {
								tmp = false;
								System.out.println("cannot move further in y");
								break;
							}

						}

					}
					canMove = tmp;
				}

				// //checks if we now can move in z direction
				if (canMove && y != 0) {

					for (int i = 0; i < brickDef.getLayout().length; i++) {
						for (int j = 0; j < brickDef.getHeighestDepth(); j++) {
							if (MainGUI.legoRoom[25 /*- width / 2*/
									- (int) Math.abs(destination.x) + i][(int) Math
										.abs(destination.y)][2 + (int) Math
									.abs(destination.z)] == 1) {
								canMove = false;
								oldDestination = new Vector3f(destination);
								System.out.println("cannot move further in y");
								break;
							}
						}
					}
				}
			} else {
				canMove = true;
			}
		} catch (Exception e) {
			// Just go on, since collisions doesn't work very well
			System.out
					.println("Some errors during collision detection... but go on without collision model");
		}

		/******************************* END CHECK METHODS FOR Y DIRECTION *******************************/

		// if we can move we have to apply the transformation and update the
		// array slots
		if (canMove) {

			// apply the transformation
			T3D.setTranslation(new Vector3f(newX, newY, newZ));
			transformGroup.setTransform(T3D);

			// TODO: Again, make nice more stable
			try {
				if (brickDef != null) {

					// update the collision array
					for (int i = 0; i < brickDef.getLayout().length; i++) {
						for (int j = 0; j < brickDef.getHeighestDepth(); j++) {

							// ******************************DEBUGGING*********************************//
							// System.out.println("set: " + (25 - width / 2 +
							// (int)
							// Math.abs(old.x)
							// + i) + ", " + (int) Math.abs(old.y) + ", " +
							// ((int)
							// Math.abs(old.z)
							// + j));
							// ************************************************************************//

							// set the new position and old position according
							// to the movement
							int newXPos = 25 /*- width / 2*/
									+ (int) (destination.x) + i;
							int oldXPos = newXPos;
							if (currentPosition.x > destination.x) {
								newXPos = 25 /*- width / 2*/// + (width -1)
										+ (int) (destination.x) + i;
								oldXPos = 25 /*- width / 2*/
										+ (brickDef.getLayout().length - 1)
										- (int) Math.abs(destination.x) + 1;
							} else if (destination.x > currentPosition.x) {
								newXPos = 25 + (int) destination.x + i;
								oldXPos = 25 + (int) destination.x - 1;
							}

							int zOffset = 0;
							// moving forward
							if (destination.z < currentPosition.z) {
								zOffset = brickDef.getHeighestDepth() - 1;
							} else if (destination.z > currentPosition.z) {
								zOffset = 0;
							}

							// set new position to 1
							MainGUI.legoRoom[newXPos][(int) Math
									.abs(destination.y)][2
									+ (int) Math.abs(destination.z) - j] = 1;

							// set the old position to 0 according to the
							// movement
							if (currentPosition.x != destination.x) {
								// System.out.println("to delete X: " +
								// oldXPos);
								MainGUI.legoRoom[oldXPos][(int) Math
										.abs(currentPosition.y)][2
										+ (int) Math.abs(currentPosition.z) - j] = 0;
							} else if (currentPosition.y != destination.y) {
								MainGUI.legoRoom[oldXPos][(int) Math
										.abs(currentPosition.y)][2
										+ (int) Math.abs(currentPosition.z) - j] = 0;
							} else {
								MainGUI.legoRoom[oldXPos][(int) Math
										.abs(currentPosition.y)][2
										+ (int) Math.abs(currentPosition.z)
										- zOffset] = 0;
							}
						}
					}
				}
			} catch (Exception e) {
				// Just go on, since collisions doesn't work very well
				System.out
						.println("Some errors during collision detection... but go on without collision model");
			}

			// update the current position
			currentPosition.add(new Vector3f(x, y, z));
			// System.out.println("current position updated: " +
			// currentPosition);
		}
	}

	/**
	 * Rotates the lego brick. It worked, but it is not implemented in the
	 * current state of the application
	 * 
	 * @param left
	 *            - flag indicating if we should rotate about left.
	 */
	public void rotate(boolean left) {
		throw new NotImplementedException();

		// First of all, exchange the width of with depth
		// if (left) {
		// this.rotation += 90;
		// this.rotation = (this.rotation == 360) ? 0 : this.rotation;
		// } else {
		// this.rotation -= 90;
		// this.rotation = (this.rotation == -90) ? 270 : this.rotation;
		// }
		//
		// //apply the transformation
		// this.T3D.setRotation(new AxisAngle4d(0, 1, 0, Math
		// .toRadians(this.rotation)));
		// this.transformGroup.setTransform(this.T3D);
	}
}
